﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows.Input;
using Dispatcher.Extensions;
using Dispatcher.ViewModels.Base;
using Repository;

namespace Dispatcher.ViewModels
{
    /// <summary>
    /// Place Search View Model.
    /// </summary>
    internal class PlaceSearchViewModel : ViewModelBase
    {
        #region Constructor

        /// <summary>
        /// Initializes a new instance of the <see cref="PlaceSearchViewModel"/> class.
        /// </summary>
        /// <param name="modelContainer">The model container.</param>
        public PlaceSearchViewModel(ModelContainer modelContainer)
        {
            ModelContainer = modelContainer;
            FromDate = DateTime.Now.Date;
            ToDate = DateTime.Now.Date;
            FromTime = new TimeSpan(9, 50, 00);
            ToTime = new TimeSpan(11, 00, 00);
            Capacity = 50;

            FreePlaces = new ObservableCollection<Place>();
            RequiredEquipmentTypes = new List<EquipmentType>();
            RequiredEquipments = new List<Equipment>();
            RequiredLocations = new List<Location>();
            RequiredPlaceTypes = new List<PlaceType>();
            SelectedPlaces = new ObservableCollection<Place>();
            ReservedEvents = new ObservableCollection<Event>();
            SelectedReservedEvents = new ObservableCollection<Event>();

            CommandBindings.Add(new CommandBinding(NavigationCommands.Search, SearchExecuted));
            CommandBindings.Add(new CommandBinding(NavigationCommands.Favorites, FavoritesExecuted));
            CommandBindings.Add(new CommandBinding(ApplicationCommands.Delete, EraseExecuted));
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets a value indicating whether [include reserved].
        /// </summary>
        /// <value>
        ///   <c>true</c> if [include reserved]; otherwise, <c>false</c>.
        /// </value>
        public bool IncludeReserved { get; set; }

        /// <summary>
        /// Gets or sets the model container.
        /// </summary>
        /// <value>
        /// The model container.
        /// </value>
        public ModelContainer ModelContainer { get; set; }

        /// <summary>
        /// Gets or sets from date.
        /// </summary>
        /// <value>
        /// From date.
        /// </value>
        public DateTime FromDate { get; set; }

        /// <summary>
        /// Gets or sets to date.
        /// </summary>
        /// <value>
        /// To date.
        /// </value>
        public DateTime ToDate { get; set; }

        /// <summary>
        /// Gets or sets from time.
        /// </summary>
        /// <value>
        /// From time.
        /// </value>
        public TimeSpan FromTime { get; set; }

        /// <summary>
        /// Gets or sets to time.
        /// </summary>
        /// <value>
        /// To time.
        /// </value>
        public TimeSpan ToTime { get; set; }

        /// <summary>
        /// Gets or sets the capacity.
        /// </summary>
        /// <value>
        /// The capacity.
        /// </value>
        public int Capacity { get; set; }

        /// <summary>
        /// Gets or sets the required locations.
        /// </summary>
        /// <value>
        /// The required locations.
        /// </value>
        public List<Location> RequiredLocations { get; set; }

        /// <summary>
        /// Gets or sets the required place types.
        /// </summary>
        /// <value>
        /// The required place types.
        /// </value>
        public List<PlaceType> RequiredPlaceTypes { get; set; }

        /// <summary>
        /// Gets or sets the required equipment types.
        /// </summary>
        /// <value>
        /// The required equipment types.
        /// </value>
        public List<EquipmentType> RequiredEquipmentTypes { get; set; }

        /// <summary>
        /// Gets or sets the required equipments.
        /// </summary>
        /// <value>
        /// The required equipments.
        /// </value>
        public List<Equipment> RequiredEquipments { get; set; }

        /// <summary>
        /// Gets or sets the free places.
        /// </summary>
        /// <value>
        /// The free places.
        /// </value>
        public ObservableCollection<Place> FreePlaces
        {
            get { return Get(() => FreePlaces); }
            set { Set(() => FreePlaces, value); }
        }

        /// <summary>
        /// Gets or sets the selected places.
        /// </summary>
        /// <value>
        /// The selected places.
        /// </value>
        public ObservableCollection<Place> SelectedPlaces
        {
            get { return Get(() => SelectedPlaces); }
            set { Set(() => SelectedPlaces, value); }
        }

        /// <summary>
        /// Gets or sets the free places.
        /// </summary>
        /// <value>
        /// The free places.
        /// </value>
        public ObservableCollection<Event> ReservedEvents
        {
            get { return Get(() => ReservedEvents); }
            set { Set(() => ReservedEvents, value); }
        }

        /// <summary>
        /// Gets or sets the selected places.
        /// </summary>
        /// <value>
        /// The selected places.
        /// </value>
        public ObservableCollection<Event> SelectedReservedEvents
        {
            get { return Get(() => SelectedReservedEvents); }
            set { Set(() => SelectedReservedEvents, value); }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Favoriteses the executed.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void FavoritesExecuted(object sender, ExecutedRoutedEventArgs e)
        {
            Reserve();
            SearchExecuted(null, null);
        }

        /// <summary>
        /// Erases the executed.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void EraseExecuted(object sender, ExecutedRoutedEventArgs e)
        {
            Erase();
            SearchExecuted(null, null);
        }

        /// <summary>
        /// Executeds the specified sender.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="executedRoutedEventArgs">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void SearchExecuted(object sender, ExecutedRoutedEventArgs executedRoutedEventArgs)
        {
            FreePlaces.Clear();
            ReservedEvents.Clear();
            var freePlaces = SearchFreePlaces();
            var reservedEvents = SearchReservedEvents();
            FreePlaces.AddRange(freePlaces);
            ReservedEvents.AddRange(reservedEvents);
        }

        /// <summary>
        /// Searches this instance.
        /// </summary>
        /// <returns>Finded places.</returns>
        private IEnumerable<Place> SearchFreePlaces()
        {
            var allPlaces = new List<Place>();

            for (var date = FromDate.Date; date <= ToDate.Date; date = date.AddDays(1))
            {
                var from = date + FromTime;
                var to = date + ToTime;
                var places =
                    ModelContainer.PlaceSet.Where(
                        place =>
                        !place.Event.Any(
                            e =>
                            (from == e.StartTime && e.EndTime == to) ||
                            (from < e.StartTime && e.StartTime < to) ||
                            (from < e.EndTime && e.EndTime < to))).ToList();

                places = places.Where(p => p.Capacity == null || p.Capacity >= Capacity).ToList();

                if (RequiredLocations.Count > 0)
                {
                    places = places.Where(p => RequiredLocations.Contains(p.Location)).ToList();
                }

                if (RequiredPlaceTypes.Count > 0)
                {
                    places = places.Where(p => RequiredPlaceTypes.Contains(p.PlaceType)).ToList();
                }

                if (RequiredEquipments.Count > 0)
                {
                    places = places.Where(p => RequiredPlaceTypes.Contains(p.PlaceType)).ToList();
                }

                if (RequiredEquipmentTypes.Count > 0)
                {
                    places = (from place in places
                              let requredEquipmentType = place.Equipment.Select(e => e.EquipmentType)
                              where RequiredEquipmentTypes.All(requredEquipmentType.Contains)
                              select place).ToList();
                }

                allPlaces.AddRange(places);
            }

            return allPlaces;
        }

        /// <summary>
        /// Searches this instance.
        /// </summary>
        /// <returns>Finded places.</returns>
        private IEnumerable<Event> SearchReservedEvents()
        {
            var organizationGroupEventGroup = GetOrganizationGroup(ModelContainer);
            var reservedEventGroup = GetReservedEventGroup(ModelContainer);
            var reservedEvents = // organizationGroupEventGroup.OrganizeEvent.ToList();

                ModelContainer.EventSet.Where(
                    e =>
                    e.OrganizePersonGroup.Id == organizationGroupEventGroup.Id &&
                    e.EventGroup.Select(g => g.Id).Contains(reservedEventGroup.Id)).ToList();

            return reservedEvents;
        }

        /// <summary>
        /// Gets the reserved event group.
        /// </summary>
        /// <param name="modelContainer">The model container.</param>
        /// <returns></returns>
        private EventGroup GetReservedEventGroup(ModelContainer modelContainer)
        {
            var groupName = "Reserved";
            var group = modelContainer.EventGroupSet.FirstOrDefault(eg => eg.Name == groupName);
            if (group == null)
            {
                var groupType = new EventGroupType {Name = "Reserved event type"};
                group = new EventGroup {Name = groupName, EventGroupType = groupType};
                modelContainer.EventGroupSet.AddObject(group);
            }

            return group;
        }

        /// <summary>
        /// Gets the reserved event group.
        /// </summary>
        /// <param name="modelContainer">The model container.</param>
        /// <returns></returns>
        private PersonGroup GetOrganizationGroup(ModelContainer modelContainer)
        {
            var groupName = "Admin group";
            var group = modelContainer.PersonGroupSet.FirstOrDefault(eg => eg.Name == groupName);
            if (group == null)
            {
                var groupType = new PersonGroupType {Name = "Admin type"};
                group = new PersonGroup {Name = groupName, PersonGroupType = groupType};
                modelContainer.PersonGroupSet.AddObject(group);
            }

            return group;
        }

        /// <summary>
        /// Erases this instance.
        /// </summary>
        private void Erase()
        {
            using (var context = new ModelContainer())
            {
                foreach (var selectedReservedEvent in SelectedReservedEvents)
                {
                    var o = context.EventSet.FirstOrDefault(ev => ev.Id == selectedReservedEvent.Id);
                    context.EventSet.DeleteObject(o);
                    var rg = GetReservedEventGroup(context);
                    rg.Event.Remove(o);
                }

                context.SaveChanges();
            }

            SelectedReservedEvents.Clear();
        }

        /// <summary>
        /// Reserves this instance.
        /// </summary>
        private void Reserve()
        {
            for (var date = FromDate.Date; date <= ToDate.Date; date = date.AddDays(1))
            {
                var from = date + FromTime;
                var to = date + ToTime;

                foreach (var place in SelectedPlaces)
                {
                    using (var context = new ModelContainer())
                    {
                        var placeId = place.Id;
                        var reserveEvent = new Event
                                               {
                                                   StartTime = @from,
                                                   EndTime = to,
                                                   Name = "ReserveEvent",
                                                   Place = context.PlaceSet.FirstOrDefault(p => p.Id == placeId)
                                               };

                        if (reserveEvent.Place == null)
                        {
                            throw new Exception(string.Format("Place[{0}] deleted by another user.", placeId));
                        }

                        var freePlaces = SearchFreePlaces().ToList();

                        if (!freePlaces.Contains(place))
                        {
                            throw new Exception(string.Format("Place[{0}] has reserved.", placeId));
                        }

                        var organizationGroupEventGroup = GetOrganizationGroup(context);
                        reserveEvent.OrganizePersonGroup = organizationGroupEventGroup;

                        var reservedEventGroup = GetReservedEventGroup(context);
                        reserveEvent.EventGroup.Add(reservedEventGroup);

                        context.EventSet.AddObject(reserveEvent);
                        context.SaveChanges();

                        context.Detach(reserveEvent);
                        ModelContainer.EventSet.Attach(reserveEvent);
                        ModelContainer.EventSet.Refresh();
                    }
                }
            }
        }

        #endregion
    }
}
